﻿#region Using directives

using System;
using System.IO;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;

using Weazel.Toad.Application.Controls;

using Weazel.Gezel.Parser;
using Weazel.Gezel.Model;
using Weazel.Gezel.Model.Errors;
using Weazel.Gezel.Simulator.Reference;

#endregion

namespace Weazel.Toad.Application.Items
{
  public class GezelSimulation
  {
    private System.ComponentModel.BackgroundWorker backgroundWorker;

    /// <summary>
    /// Raised when a simulation is started
    /// </summary>
    public event EventHandler<SimulationEventArgs> SimulationStarted;

    /// <summary>
    /// Raised when a running simulation has text to be written to the console
    /// or other similar device
    /// </summary>
    public event EventHandler<SimulationOutputReadyEventArgs> SimulationOutputReady;

    /// <summary>
    /// Raised when a simulation ends
    /// </summary>
    public event EventHandler<SimulationEventArgs> SimulationDone;

    private GezelSimulationOutputWriter outputWriter =
      new GezelSimulationOutputWriter();
		
    /// <summary>
    /// The Gezel file containing the model of this simulation
    /// </summary>
    private GezelTextFile file;
    public GezelTextFile File
    {
      get
      {
        return file;
      } 
    }   

    /// <summary>
    /// Cycles to run
    /// </summary>
    private int cyclesToRun = 10;
    public int CyclesToRun
    {
      get
      {
        return cyclesToRun;
      }

      set
      {
        cyclesToRun = value;
      }
    }

    private Simulation simulation = null;

    /// <summary>
    /// the output of the last or a current simulation
    /// </summary>
    private StringBuilder outputBuffer = new StringBuilder();
    public StringBuilder OutputBuffer
    {
      get
      {
        return outputBuffer;
      }
    }

    private bool isRunning = false;
    public bool IsRunning
    {
      get
      {
        return isRunning;
      }
    }

    private Model gezelModel;

    private List<ParseError> parseErrors = new List<ParseError>();
    public List<ParseError> ParseErrors
    {
      get { return parseErrors; }
    }

    private List<VerificationError> verificationErrors = new List<VerificationError>();
    public List<VerificationError> VerificationErrors
    {
      get { return verificationErrors; }
    }

		private List<VerificationWarning> warnings = new List<VerificationWarning>();
		public List<VerificationWarning> Warnings
		{
			get { return warnings; }			
		}

    public GezelSimulation(GezelTextFile file)
    {
      // create background worker and register events
      this.backgroundWorker = 
        new System.ComponentModel.BackgroundWorker();

      this.backgroundWorker.WorkerSupportsCancellation = true;
      this.backgroundWorker.WorkerReportsProgress = true;      

      this.backgroundWorker.DoWork += 
        new System.ComponentModel.DoWorkEventHandler(backgroundWorker_DoWork);
      this.backgroundWorker.ProgressChanged += 
        new System.ComponentModel.ProgressChangedEventHandler(backgroundWorker_ProgressChanged);
      this.backgroundWorker.RunWorkerCompleted += 
        new System.ComponentModel.RunWorkerCompletedEventHandler(backgroundWorker_RunWorkerCompleted);

			this.outputWriter.OutputReadyEvent += 
				new GezelSimulationOutputWriter.OutputReadyEventHandler(outputWriter_OutputReadyEvent);

      // save file reference
      this.file = file;
    }

    private bool parseAndVerify()
    {
      // clear previous errors
      parseErrors.Clear();
      verificationErrors.Clear();

      try
      {
        string description = (string) file.Contents;

        Model model = ParseModel.Parse(ref parseErrors, description, file.DisplayName);

        if (parseErrors.Count == 0)
          model.Verify(ref verificationErrors, ref warnings);

        if (parseErrors.Count == 0 /* && verificationErrors.Count  == 0 */)
          gezelModel = model;

        return parseErrors.Count == 0 && verificationErrors.Count == 0;
      }
      catch (ParseException)
      {
        // ignore certain types of exceptions (non-terminated strings etc)             
      }
      catch (Exception e)
      {
        string message =
          string.Format(
            "Internal error: {0} ({1})",
            e.Message,
            e.GetType().ToString()
          );

        System.Windows.Forms.MessageBox.Show(message);
      }

      return false;
    }

    public void Run()
    {
      System.Diagnostics.Debug.Assert(!isRunning, "simulation already running");

      if (parseAndVerify())
      {
        isRunning = true;

        // switch to output ..
        Application.Instance.MainForm.LayoutControl.ContainerPanel.SetCurrentPage("Gezel Simulation Output");

        //Application.Instance.MainForm.LayoutControl.ContainerPanel.CurrentPage =
        //  Weazel.Toad.Application.Controls.ContainerPanel.ContainerPanelPages.Output;

				// clear any existing output
        Application.Instance.MainForm.LayoutControl.ContainerPanel.GetConsoleOutputControl("Gezel Simulation Output").Clear();

        // write status
        Application.Instance.MainForm.SimulationStatusPanel.Text =
          string.Format(
            "Simulating '{0}' : {1} cycles",
            file.DisplayName,
            cyclesToRun
          );

        // clear output
        outputBuffer.Remove(0, outputBuffer.Length);

        if (SimulationStarted != null)
          SimulationStarted(this, new SimulationEventArgs(file));     

        // start the simulation (in the background)
        backgroundWorker.RunWorkerAsync();        
        return;
      }
      else
      {
        string message = "Failed to parse and verify model. Correct errors and try again";
        System.Windows.Forms.MessageBox.Show(message);
        return;
      }
    }

    public void End()
    {
      if (IsRunning)
      {
        backgroundWorker.CancelAsync();

        // will cause the simulation loop to abort
        simulation.Finish();

        Application.Instance.MainForm.SimulationStatusPanel.Text =
          "Simulation ended";
      }      
    }

    void backgroundWorker_DoWork(object sender, System.ComponentModel.DoWorkEventArgs args)
    {      
      // create the simulation object 
      simulation = new Simulation();      

      // register a custom event handler. This handler will raise a second event
      simulation.OutputWriter = outputWriter;    

      // expections are handled in the 'RunWorkerCompleted' event handler
      args.Result = simulation.SetupAndRun(gezelModel, cyclesToRun);

      simulation.WriteToggleCounts();

      simulation.CleanUp();      

      // return: result is processed in the OnWorkCompleted handler
    }

    void outputWriter_OutputReadyEvent(string text)
    {
      // Running multiple simulations will cause the main thread to be monopolized by
      // serving the event handler handling the 'ReportProgress' event - this delay 
      // makes sure this does not happen.
      System.Threading.Thread.Sleep(10);

      // relay to main thread..
      backgroundWorker.ReportProgress(1, text);
    }

    void backgroundWorker_RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e)
    {
      isRunning = false;
      
      if (e.Error != null)
      {
        // this is highly unexpected (simulation errors are reported in the result object)
        Forms.ExceptionForm.Show(e.Error);
      }

      // check the simulation result object
      if (e.Result == null || !(e.Result is SimulationResult))
      {
        System.Windows.Forms.MessageBox.Show("Unknown error encountered during simulation");
      }
      else
      {
        SimulationResult result = e.Result as SimulationResult;

        if (result.Exception != null)
        {
          // PL: disabled 
          // an expection was raised during simulation
          Forms.ExceptionForm.Show(result.Exception); 

          // show a human readable message ..
          System.Windows.Forms.MessageBox.Show(result.Message);

          // we cannot flush output here!
        }

        Application.Instance.MainForm.SimulationStatusPanel.Text =
          "Simulation done: " + result.Message;
      }

      if (SimulationDone != null)
        SimulationDone(this, new SimulationEventArgs(file));
    }

    void backgroundWorker_ProgressChanged(object sender, System.ComponentModel.ProgressChangedEventArgs e)
    {
      string text = (string)e.UserState;

      // append to output buffer
      outputBuffer.Append(text);
      
      // notify any listning components ..
      if (SimulationOutputReady != null)
        SimulationOutputReady(this, new SimulationOutputReadyEventArgs(text));
    }
  }
}
